package org.dawn.deveco.plugin.resourceeditor.window;


import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.ui.ColorPicker;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.ui.components.JBPanel;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.ui.components.JBTabbedPane;
import com.intellij.ui.table.JBTable;
import org.dawn.deveco.plugin.resourceeditor.common.Constants;
import org.dawn.deveco.plugin.resourceeditor.component.JExtensionPanel;
import org.dawn.deveco.plugin.resourceeditor.component.JExtensionPanelAdaptor;
import org.dawn.deveco.plugin.resourceeditor.model.MediaResourceContent;
import org.dawn.deveco.plugin.resourceeditor.model.interfaces.ResourceContent;
import org.dawn.deveco.plugin.resourceeditor.utils.CommonUtil;
import org.dawn.deveco.plugin.resourceeditor.utils.FileUtils;
import org.dawn.deveco.plugin.resourceeditor.utils.Logger;
import org.dawn.deveco.plugin.resourceeditor.utils.SwingUtils;
import org.dawn.deveco.plugin.resourceeditor.viewmodel.ResourceFileHelper;
import org.dawn.deveco.plugin.resourceeditor.window.model.MyTableModel;
import org.dawn.deveco.plugin.resourceeditor.window.utils.NotificationHelper;

import javax.swing.Timer;
import javax.swing.*;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class MyToolWindow {
    // region Swing 组件
    private final JBPanel contentPanel = new JBPanel();
    private JTabbedPane tabbedPanel;
    private JPanel toolbar;
    private JButton buttonLoad;
    private JButton buttonSave;
    private final Map<String, Map<String, JBTable>> tableMap = new HashMap<>();
    // endregion

    // region 状态变量
    private String currentCategory = "";
    private final Map<String, ResourceContent> resourceContentMap = new HashMap<>();
    // endregion

    // region 配置数据
    private String[] categories;
    // endregion

    private static final Logger LOG = Logger.getInstance(MyToolWindow.class);
    private final ResourceFileHelper helper = new ResourceFileHelper();
    private final ToolWindow toolWindow;

    private final ActionListener deleteHandler = e -> {
        JBTable table = (JBTable) e.getSource();
        MyTableModel model = (MyTableModel) table.getModel();
        int row = table.getSelectedRow();

        int result = Messages.NO;
        String name = (String) model.getValueAt(row, 0);
        String value = (String) model.getValueAt(row, 1);
        String message;
        if (Constants.MEDIA_CATEGORY.equals(model.getCategory())) {
            value = (String) model.getValueAt(row, MediaResourceContent.FULL_FILENAME_COLUMN_INDEX);
            message = String.format("确定删除 Media 数据项吗?\n(注意：图像文件将被物理删除。)\n\n    Name: %s\n    FileName: %s", name, value);
        } else {
            message = String.format("确定删除 %s [%s] 数据项吗?\n\n    Name: %s\n    Value: %s", model.getCategory(), model.getGroup(), name, value);
        }
        result = Messages.showYesNoDialog(message, "", Messages.getQuestionIcon());
        if (result == Messages.YES) {
            model.setChanged(true);
            model.removeRow(row);
        }
    };

    private final TableModelListener tableModelListener = e -> {
        if (!(e.getSource() instanceof MyTableModel)) {
            return;
        }

        MyTableModel m = (MyTableModel) e.getSource();
        if (!m.isChanged()) {
            return;
        }

        String cate = m.getCategory();
        JTable table = m.getTable();
        LOG.debugF("modellistener", "isAdd: %b, category: %s, edit at: (%d, %d)",
                m.isAddNew(), cate,
                table.getEditingRow(), table.getEditingColumn());
        boolean saved = m.syncToResourceContent();
        LOG.debugF("modellistener", "category: %s, save data: %b", m.getResourceContent().getCategory(), saved);
        m.setChanged(false);
        m.setAddNew(false);

//        if (e.getType() == TableModelEvent.UPDATE) {
//            boolean saved = m.syncToResourceContent();
//            LOG.debugF("modellistener", "category: %s, save data: %b", m.getResourceContent().getCategory(), saved);
//            m.setChanged(false);
//            m.setAddNew(false);
//        } else if (e.getType() == TableModelEvent.INSERT) {
//        }
    };

    public List<AnAction> getTitleActions() {
        ImageIcon icon = SwingUtils.shrinkIcon(Constants.LOAD_RESOURCE_ICON_PATH, 13);
        List<AnAction> ret = new ArrayList<>();
        ret.add(new AnAction("加载资源文件", "从项目资源文件加载数据", icon) {
            public void actionPerformed(AnActionEvent e) {
                loadResource();
            }
        });
        return ret;
    }

    private void loadResource() {
        Path path = Paths.get(Objects.requireNonNull(this.toolWindow.getProject().getBasePath()), "entry\\src\\main\\resources");
        String folderPath = "";
        if (!Files.isDirectory(path)
                || !Files.exists(Paths.get(path.toString(), Constants.RESOURCE_PATH_MAP.get(Constants.STRING_CATEGORY)))) {
            folderPath = SwingUtils.chooseFolder(this.toolWindow.getProject(), "选择资源文件根目录", "选择项目根目录下的 entry/src/main/resources 目录。然后插件将加载其子目录（base/element）中的资源文件，例如：string.json、color.json 等。");
            folderPath = FileUtils.convertToOsPath(folderPath);
            if (folderPath == null || !Files.exists(Paths.get(folderPath, Constants.RESOURCE_PATH_MAP.get(Constants.STRING_CATEGORY)))) {
                String msg = "没有选择有效的资源根目录，无法加载资源文件！";
                msg = folderPath != null ? msg + "\n" + folderPath : msg;
                Messages.showMessageDialog(msg, "错误", Messages.getErrorIcon());
                return;
            }
        }
        Constants.RESOURCE_ROOT_PATH = folderPath;

        identifyResource();
        initToolWindow();
    }

    private boolean identifyResource() {
        // 获取目录中的资源文件名
        Path path = Paths.get(Constants.RESOURCE_ROOT_PATH, Constants.RESOURCE_FILE_SUB_PATH);
        try (Stream<Path> list = Files.list(path)) {
            List<Path> mediaFiles = list.filter(Files::isRegularFile).filter(p -> p.toString().endsWith(Constants.RESOURCE_FILE_EXTENSION)).collect(Collectors.toList());
            for (Path p : mediaFiles) {
                String fileName = p.toFile().getAbsolutePath();
                fileName = fileName.replace(Constants.RESOURCE_ROOT_PATH, "").substring(1);
                String category = p.toFile().getName().replace(Constants.RESOURCE_FILE_EXTENSION, "");
                category = category.substring(0, 1).toUpperCase() + category.substring(1).toLowerCase();
                Constants.RESOURCE_PATH_MAP.put(category, fileName);
            }

            int count = Constants.RESOURCE_PATH_MAP.size();
            this.categories = Constants.RESOURCE_PATH_MAP.keySet().toArray(new String[count]);
            // 将 Media 移动到数组末尾
            for (int i = 0; i < this.categories.length; i++) {
                if (Constants.MEDIA_CATEGORY.equals(this.categories[i])) {
                    String temp = this.categories[i];
                    this.categories[i] = this.categories[count - 1];
                    this.categories[count - 1] = temp;
                    break;
                }
            }
            return true;
        } catch (IOException e) {
            LOG.error("从资源目录获取文件名信息失败 " + Constants.RESOURCE_ROOT_PATH, e);
        }
        return false;
    }

    public MyToolWindow(ToolWindow toolWindow) {
        this.toolWindow = toolWindow;

        contentPanel.setLayout(new java.awt.BorderLayout());

        ApplicationManager.getApplication().invokeLaterOnWriteThread(() -> {
            if (isValidMediaRootPath()) {
                identifyResource();
                initToolWindow();
            } else {
                String iconBase64 = "iVBORw0KGgoAAAANSUhEUgAAACUAAAAgCAYAAACVU7GwAAAAAXNSR0IArs4c6QAAAvJJREFUWEftmE1oE1EQx//zmt2UGjcN4sFCoaAgUlDEiyBIC0Jtu6mIoAcvpqIepF7USz20ggp+XCpeJKVW9FQPQrOJRQ/Wiyf14EGoCu3B4gdCsoG2ZpPsyG6yoYQ2m7RLQMk75GPe7Px/mZ33Mm8JxSGnYrdBdALATsdWx/f3YJ4yWsN3LE2yXmRdmwfQUUeItaUYJ41W9Rn503GVmWNFrwWY/LjucIJGHE0Dvm3kT2qXWeCeZfT5fG3LW45+rzeUrMdOA/TU0mXQQZKTsVEUSY2gat/Oeg9fMtYlBL22dE2Tu/9TqJQWkgVHmekwEaKGol6rNdOeZkr+re0hCVEGDpVACBOGop6tBcwzqGKgKIBdawAkpKb84FLg2M9q4DyBklOxUyAaBxBYLcqEWWJ02TbmD/D5Ikag96Mb2Kah/Hr8EoPHSkLMwyC6ZXNYUCbNgzhizxMWyeTBTGv4ZSWwTUHJae0mGMOOAIGHMsHwA1nX2IHKKmq3pMfHCHyp6JdnmJFscODJemAbhvKntXFmFAqYscQCF7OKau/+5VBF2w0ApZVIhKsZRbU36fKxISgpOX2GhHhUDPaDiM5llH7NCb4WlDXnT2tXmHG3lFmi3Rml/7MnUH59upchEgDPmUSDOUV9uzrwelCWj5SOR4h5wvrcJIn2lZa+b55A2cGTif3Zlfwc2sLL5UErQVm+zcnnHX9Cxxc8r6lKq8cNqtK11tyGasotaAPKLUNuq6/a6xu3r5GpWvcpt4w1asotQ878v5EpKaWdJ8JDm1qITmNr36dqf6FX+5Rf14YYuG/Hy+f3UXNKO2ISXtkGxiILfHHEOM/Xc6HwrBtkrX8zsh4fYeJC21zQ7SRgO8BfDWVlr/MsYQZAT7m41d5anaSXUKvrpzwuwezLBAdelE7EhaOz6GHidseZ8vzGCIVHq4B6B+AACJOGohb68/XGr6mA1NwSKx0wLD/CJEwkrIcbha8ejJalmR05I3tBIDdZqW+qVuovmFw+C88fn5oAAAAASUVORK5CYII=";
                String msg = "<span>请点击 " + Constants.PLUGIN_NAME + " 工具窗标题栏中的按钮</span>";
                msg = msg + "&nbsp;&nbsp;&nbsp;<img width='32' height='32' src='data:image/png;base64," + iconBase64 + "'>";
                msg = msg + "，<br/><span>选择资源根目录！</span>";
                Messages.showMessageDialog(msg, Constants.PLUGIN_NAME + " 插件提示", Messages.getInformationIcon());
            }
        });
    }

    private boolean isValidMediaRootPath() {
        Path path = Paths.get(Objects.requireNonNull(this.toolWindow.getProject().getBasePath()), "entry\\src\\main\\resources");
        return Files.exists(Paths.get(path.toString(), Constants.RESOURCE_PATH_MAP.get(Constants.STRING_CATEGORY)));
    }

    private void initToolWindow() {
        try {
            if (this.contentPanel.getComponentCount() > 0) {
                // 清空现有组件
                this.contentPanel.removeAll();
            }

            contentPanel.setLayout(new java.awt.BorderLayout());

            this.currentCategory = this.categories[0];

            this.tabbedPanel = new JBTabbedPane(SwingConstants.TOP);
            Arrays.stream(this.categories).forEach(category -> {
                tableMap.put(category, new HashMap<>());
                try {
                    Component categoryPanel = createCategoryPanel(category);
                    JBScrollPane scrollPane = new JBScrollPane(categoryPanel);
                    scrollPane.setName(category);
                    this.tabbedPanel.addTab(category, scrollPane);
                } catch (IllegalStateException e) {
                    // 通知数据资源加载失败
//                    ToolWindowManager.getInstance(toolWindow.getProject()).notifyByBalloon(
//                            this.toolWindow.getId(), MessageType.ERROR,
//                            e.getMessage()
//                    );
                    NotificationHelper.notifyError(this.toolWindow.getProject(), e.getMessage());
                }
            });
            tabbedPanel.addChangeListener(e -> {
                if (tabbedPanel.getSelectedComponent().getName().equals(this.currentCategory)) return;
                this.currentCategory = tabbedPanel.getSelectedComponent().getName();
            });

            contentPanel.add(this.tabbedPanel, BorderLayout.CENTER);  //布局的中间

            LOG.info("窗体创建完成");
        } catch (Exception ex) {
            LOG.error("未完成ToolWindow初始化", ex);
        }
    }

    private void createToolbar() {
        this.toolbar = new JPanel();
        this.buttonLoad = new JButton("加载资源");
        this.toolbar.add(this.buttonLoad);
    }

    private Component createCategoryPanel(final String category) throws IllegalStateException {
        ResourceContent tableContent = helper.loadResource(category);
        if (ResourceContent.LoadingState.SUCCESS_LOADED != tableContent.getLoadingState()) {
            throw new IllegalStateException(tableContent.getLoadingState().getMessage());
        }
        LOG.debugF("createCategoryPanel", "category: %s - %s", tableContent.getCategory(), tableContent.getGroupList());
        this.resourceContentMap.put(category, tableContent);

        JPanel panel = new JPanel();
        JExtensionPanelAdaptor extensionPanelAdaptor = new JExtensionPanelAdaptor(panel);

        for (String group : tableContent.getGroupList()) {
            JBTable groupTable = createTable(tableContent, group);
            JExtensionPanel.ActionButtonParam[] actionButtons = new JExtensionPanel.ActionButtonParam[]{
                    new JExtensionPanel.ActionButtonParam("", Constants.ADD_ICON, new AddAction(groupTable))
            };
            extensionPanelAdaptor.add(group, actionButtons, groupTable, true);
            this.tableMap.get(category).put(group, groupTable);
        }

        return panel;
    }

    private JBTable createTable(ResourceContent tableContent, String group) {
        MyTableModel model = new MyTableModel(tableContent, group);
        model.addTableModelListener(this.tableModelListener);

        JBTable table = new JBTable(model);
        table.setFillsViewportHeight(true);
        model.setTable(table);

        bindTableListener(table);

//        new TableCellListener(category, table, new AbstractAction() {
//            public void actionPerformed(ActionEvent e) {
//                // 编辑表格单元完成，数据已被更改
//
//                TableCellListener tcl = (TableCellListener) e.getSource();
//                isChanged.put(tcl.getCategory(), true);
//                LOG.debugF("TableCellListener.action", "cell changed, category: %s", tcl.getCategory());
//                LOG.info("TableCellListener.action", "Row : " + tcl.getRow());
//                LOG.info("TableCellListener.action", "Column: " + tcl.getColumn());
//                LOG.info("TableCellListener.action", "Old : " + tcl.getOldValue());
//                LOG.info("TableCellListener.action", "New : " + tcl.getNewValue());
//            }
//        });

        // 加载末尾的删除按钮列
        TableColumn deleteCol = table.getColumnModel().getColumn(model.getColumnCount() - 1);
        DeleteButtonRenderer deleteRenderer = new DeleteButtonRenderer();
        deleteCol.setCellRenderer(deleteRenderer);
        deleteCol.setMinWidth(Constants.DELETE_ICON_SIZE);
        deleteCol.setMaxWidth(Constants.DELETE_ICON_SIZE);

        TableCellRenderer tcr = new DefaultTableCellRenderer() {
            @Override
            public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
                Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
                String colorValue = (String) table.getValueAt(row, 1);
                Color color = table.getBackground();
                if (!CommonUtil.isBlank(colorValue) && colorValue.matches("^#[0-9aAbBcCdDeEfF]{6}$")) {
                    color = Color.decode(colorValue);
                }
                c.setBackground(color); // 设置背景色
                return c;
            }
        };

        String colorKey = Constants.RESOURCE_PATH_MAP.keySet().stream().filter("color"::equalsIgnoreCase).findFirst().orElse("");
        if (colorKey.equalsIgnoreCase(tableContent.getCategory())) {
            table.getColumnModel().getColumn(2).setCellRenderer(tcr);
        }

        // 设置隐藏列
        for (int i = 0; i < tableContent.getColumnVisible().length; i++) {
            boolean visible = tableContent.getColumnVisible()[i];
            if (visible)
                continue;
            TableColumn column = table.getColumnModel().getColumn(i);
            column.setPreferredWidth(0);
            column.setMinWidth(0);
            column.setMaxWidth(0);
        }

        // 设置表格可编辑
        table.setEnabled(true);
        table.setCellSelectionEnabled(true);
        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

        return table;
    }

    private void bindTableListener(JBTable table) {
        table.addFocusListener(new FocusAdapter() {
            private final Logger LOG = Logger.getInstance(FocusListener.class);
            private final java.util.List<String> ignoredButtonNames =
                    Arrays.asList("Add", "Save", "Discard");

            @Override
            public void focusLost(FocusEvent e) {
                if (e.getSource() instanceof JButton
                        && ignoredButtonNames.contains(((JButton) e.getSource()).getText())) {
                    return;
                }
                if (e.getSource() instanceof JTable) {
                    // TODO 判断表格当前状态，如果是编辑状态，则保存数据
                    // TableModel.newDataAvailable() 触发TableModelListener
                    return;
                }
                LOG.debug("focusLost", e.toString());
            }
        });

        table.addKeyListener(new KeyAdapter() {
            private final Logger LOG = Logger.getInstance(KeyListener.class);

            @Override
            public void keyTyped(KeyEvent e) {
                printKeyEvent(e, "keyTyped");
            }

            @Override
            public void keyPressed(KeyEvent e) {
                printKeyEvent(e, "keyPressed");
            }

            @Override
            public void keyReleased(KeyEvent e) {
                printKeyEvent(e, "keyReleased");
            }

            private final java.util.List<Integer> keyCodes = Arrays.asList(KeyEvent.VK_ENTER,
                    KeyEvent.VK_UP,
                    KeyEvent.VK_DOWN,
                    KeyEvent.VK_LEFT,
                    KeyEvent.VK_RIGHT,
                    KeyEvent.VK_HOME,
                    KeyEvent.VK_END,
                    KeyEvent.VK_PAGE_UP,
                    KeyEvent.VK_PAGE_DOWN);

            private void printKeyEvent(KeyEvent e, String action) {
                // 是否通过按键改变了当前选中的行或列
                if (this.keyCodes.contains(e.getKeyCode())) {
                    // TODO 如果当前处于编辑状态，则保存数据
                    // TableModel.newDataAvailable() 触发TableModelListener
                    int row = table.getEditingRow();
                    int col = table.getEditingColumn();
                    int selectedRow = table.getSelectedRow();
                    int selectedCol = table.getSelectedColumn();
                    int selectedRowCount = table.getSelectedRowCount();
                    int selectedColumnCount = table.getSelectedColumnCount();
                    LOG.debugF(action, "KeyChar:%d, selected(%d, %d), selectedCount(%d, %d), edit at:(%d, %d)",
                            (int) e.getKeyChar(), selectedRow, selectedCol, selectedRowCount, selectedColumnCount,
                            row, col);
                }
            }
        });

        table.addMouseListener(new MouseAdapter() {
            private final Logger LOG = Logger.getInstance(MouseListener.class);

            @Override
            public void mouseClicked(MouseEvent e) {
                int row = table.rowAtPoint(e.getPoint());
                int col = table.columnAtPoint(e.getPoint());

                if (!(row >= 0 && row < table.getRowCount()
                        && col >= 0 && col < table.getColumnCount())) {
                    return;
                }

                // 判断是否点击了末尾列的删除按钮
                if (col == table.getColumnCount() - 1 && !table.isCellEditable(row, col)) {
                    deleteHandler.actionPerformed(new ActionEvent(table, ActionEvent.ACTION_PERFORMED, "delete"));
                    return;
                }

                String colorKey = Constants.RESOURCE_PATH_MAP.keySet().stream().filter("color"::equalsIgnoreCase).findFirst().orElse("");
                if (colorKey.equals(currentCategory) && col == 1) {
                    // 弹出颜色选择器
                    MyTableModel model = (MyTableModel) table.getModel();
                    String value = (String) model.getValueAt(row, col);
                    Color color = Color.WHITE;
                    if (!CommonUtil.isBlank(value) && value.matches("^#[0-9aAbBcCdDeEfF]{6}$")) {
                        color = Color.decode(value);
                    }
                    color = ColorPicker.showDialog(table, "选择颜色", color, false, null, false);
                    if (color != null) {
                        model.setChanged(true);
                        int rgb = color.getRGB() & 0x00FFFFFF;
                        value = "#" + Integer.toHexString(rgb).toUpperCase();
                        model.setValueAt(value, row, col);
                    }
                }

                LOG.debugF("mouseClicked", "cell at row=%d, col=%d", row, col);
            }
        });

        table.addMouseMotionListener(new MouseMotionAdapter() {
            private int currentRowInMouseHover = -1;
            private Balloon balloonPopup = null;
            private Timer closeTimer = null;

            public void mouseMoved(MouseEvent e) {
                int row = table.rowAtPoint(e.getPoint());
                int col = table.columnAtPoint(e.getPoint());

                if (!(row >= 0 && row < table.getRowCount()
                        && col >= 0 && col < table.getColumnCount())) {
                    return;
                }

                if (Constants.MEDIA_CATEGORY.equals(currentCategory) && col == MediaResourceContent.ICON_COLUMN_INDEX) {
                    if (currentRowInMouseHover != row) {
                        currentRowInMouseHover = row;
                        if (this.closeTimer != null) {
                            this.closeTimer.stop();
                            this.closeTimer = null;
                        }
                        if (balloonPopup != null && !balloonPopup.isDisposed()) {
                            balloonPopup.dispose();
                        }
                        // 在 Media 资源表格中，点击缩略图列，在弹出窗口中显示原图
                        balloonPopup = popupMediaItem((String) table.getModel().getValueAt(row, MediaResourceContent.FULL_FILENAME_COLUMN_INDEX), e);

                        // 定时自动关闭
                        this.closeTimer = new Timer(1500, eve -> balloonPopup.dispose());
                        this.closeTimer.start();
                    }
                }
            }
        });

        table.addPropertyChangeListener(new PropertyChangeListener() {
            private final Logger LOG = Logger.getInstance(PropertyChangeListener.class);
            private final java.util.List<String> ignoredProperties =
                    Arrays.asList("ancestor", "ExpandedRenderer", "TableHoveredRow", "columnSelectionAllowed", "cellSelectionEnabled");

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                if (ignoredProperties.stream().anyMatch(x -> x.equals(evt.getPropertyName()))
                        || evt.getPropertyName().startsWith("graphics")
                        || evt.getPropertyName().startsWith("uiInspector")
                ) {
                    return;
                }

                if (evt.getPropertyName().equals("tableCellEditor")) {
//                    LOG.debugF("propertyChange", "tableCellEditor changed: old:%s, new:%s, propagationId:%s",
//                            evt.getOldValue(), evt.getNewValue(), evt.getPropagationId());
                    JTable table = (JTable) evt.getSource();
                    MyTableModel model = (MyTableModel) table.getModel();
                    int row = table.getSelectedRow();
                    int col = table.getSelectedColumn();
                    Object cellValue = table.getValueAt(row, col);

                    if (evt.getOldValue() == null) {
                        // 编辑状态开始。如果是新增行，value = ""
                        model.setOldValue(cellValue);
                    } else {
                        // 编辑状态结束
                        if (cellValue.equals(model.getOldValue())) {
                            // 没有修改
                            model.setChanged(false);
                            if (model.isAddNew()) {
                                // 删除新增行
                                model.setAddNew(false);
                                ((MyTableModel) table.getModel()).removeRow(row);
                            }
                        } else {
                            LOG.debugF("propertyChange", "(%d, %d), value changed from \"%s\" to \"%s\"", row, col, model.getOldValue(), cellValue);
                            model.setChanged(true);
                            model.fireTableCellUpdated(row, col);
                        }
                    }
                    return;
                }
                LOG.debug("propertyChange", evt.toString());
            }
        });

    }

    private Balloon popupMediaItem(String iconPath, MouseEvent event) {
        Project project = this.toolWindow.getProject();
        if (Objects.isNull(project)) {
            return null;
        }

        ImageIcon icon = new ImageIcon(iconPath);
        JLabel imageLabel = new JLabel(icon);
        imageLabel.setVerticalAlignment(JLabel.CENTER);
        imageLabel.setHorizontalAlignment(JLabel.CENTER);
        imageLabel.setPreferredSize(new Dimension(icon.getIconWidth(), icon.getIconHeight()));

        JPanel panel = new JPanel(new BorderLayout());
        panel.add(imageLabel, BorderLayout.CENTER);

        // 弹出窗居中显示
        // ComponentPopupBuilder builder = JBPopupFactory.getInstance().createComponentPopupBuilder(panel, null);
        // builder.setModalContext(true);
        // JBPopup jbPopup = builder.createPopup();
        // bPopup.showCenteredInCurrentWindow(project);

        // 气泡弹出窗显示在鼠标位置
        Balloon balloon = JBPopupFactory.getInstance().createBalloonBuilder(panel).createBalloon();
        balloon.show(new RelativePoint(event), Balloon.Position.below);

        imageLabel.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
                balloon.dispose();
            }
        });

        return balloon;
    }

    private boolean anyChanged() {
        return this.resourceContentMap.values().stream().anyMatch(ResourceContent::isChanged);
    }

    public JComponent getContentPanel() {
        return this.contentPanel;
    }

    public final class DeleteButtonRenderer extends DefaultTableCellRenderer {
        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
            int rowHeight = table.getRowHeight();
            int iconHeight = rowHeight - 4;
            JLabel deleteIconLabel = new JLabel();
            ImageIcon icon = Constants.DELETE_ICON;
            Image img = icon.getImage().getScaledInstance(iconHeight, iconHeight, Image.SCALE_SMOOTH);
            icon = new ImageIcon(img);
            deleteIconLabel.setIcon(icon);
//            Dimension deleteBtnSize = new Dimension(iconHeight, iconHeight);
//            deleteIconLabel.setMinimumSize(deleteBtnSize);
//            deleteIconLabel.setMaximumSize(deleteBtnSize);
            deleteIconLabel.setHorizontalAlignment(DefaultTableCellRenderer.CENTER);
            return deleteIconLabel;
        }
    }

    private class AddAction extends AbstractAction {
        private final JTable table;

        public AddAction(JTable table) {
            this.table = table;
        }

        public void actionPerformed(ActionEvent e) {
            MyTableModel model = (MyTableModel) table.getModel();

            if (Constants.MEDIA_CATEGORY.equals(model.getCategory())) {
//                MediaInputDialog dialog = new MediaInputDialog(null);
//                dialog.showAndGet();
//                if (dialog.isOK()) {
//                    String selectedFilePath = dialog.getSelectedFilePath();
                String selectedFilePath = SwingUtils.chooseImageFile(toolWindow.getProject(), "选择图像文件", "请选择要添加的图像文件 (.png, .jpg, .jpeg, .gif, .bmp)");
                if (selectedFilePath != null) {
                    File file = new File(selectedFilePath);
                    Vector<Object> rowData = new Vector<>();
                    rowData.add(file.getName().substring(0, file.getName().lastIndexOf('.')));
                    rowData.add(file.getName());
                    rowData.add(selectedFilePath);
                    rowData.add(SwingUtils.shrinkIcon(selectedFilePath, Constants.MEDIA_ICON_SIZE));
                    rowData.add("");
                    addRowIntoTable(table, model, rowData);
                }
                return;
            }

            addRowIntoTable(table, model, null);
        }

        private void addRowIntoTable(JTable table, MyTableModel model, Vector rowData) {
            if (rowData != null) {
                model.setAddNew(true);
                model.setChanged(true);
                model.addRow(rowData);
                // 使单元格可见
                Rectangle rect = table.getCellRect(table.getRowCount() - 1, 0, false);
                table.scrollRectToVisible(rect);
                return;
            }

            model.setAddNew(true);
            model.setChanged(true);
            model.addRow();
            table.requestFocusInWindow();
            int row = table.getRowCount() - 1;
            int col = 0;

            // 使单元格可见
            Rectangle rect = table.getCellRect(row, col, false);
            table.scrollRectToVisible(rect);

            table.setRowSelectionInterval(row, row);
            table.setColumnSelectionInterval(col, col);
            boolean edit = table.editCellAt(row, col);
        }
    }

    // region 单例测试
    public static void main(String[] args) {
//        Locale.setDefault(Locale.CHINESE);
        SwingUtilities.invokeLater(() -> {
//            JFrame.setDefaultLookAndFeelDecorated(true);
            JFrame frame = new JFrame("Test");
            frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

            MyToolWindow window = new MyToolWindow(null);

            frame.add(window.getContentPanel());
            frame.pack();
            frame.setVisible(true);
            frame.setSize(1200, 900);
            showCenter(frame);
        });
    }

    private static void showCenter(JFrame frame) {
//        GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
//        int screenWidth = gd.getDisplayMode().getWidth();
//        int screenHeight = gd.getDisplayMode().getHeight();
        Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
        int screenWidth = screen.width;
        int screenHeight = screen.height;
        int x = (screenWidth - frame.getWidth()) / 2;
        int y = (screenHeight - frame.getHeight()) / 2;
        frame.setLocation(x, y);
    }
    // endregion
}